You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
326 lines
8.2 KiB
326 lines
8.2 KiB
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
|
|
const ownerRecord = {
|
|
publicSlug: "demo-user",
|
|
nickname: "Demo",
|
|
avatar: "https://cdn.example.com/avatar.png",
|
|
avatarVisibility: "public",
|
|
bioVisibility: "public",
|
|
bioMarkdown: "hello bio",
|
|
socialLinksJson: "[]",
|
|
status: "active",
|
|
};
|
|
|
|
const selectMock = mock(() => ({
|
|
from: () => ({
|
|
where: () => ({
|
|
limit: async () => [ownerRecord],
|
|
}),
|
|
}),
|
|
}));
|
|
|
|
const getPublicHubBySlug = mock(async () => ({
|
|
modules: {
|
|
posts: {
|
|
items: [
|
|
{ slug: "hub-p1", title: "hub-post-1" },
|
|
{ slug: "hub-p2", title: "hub-post-2" },
|
|
],
|
|
total: 6,
|
|
},
|
|
timeline: {
|
|
items: [
|
|
{ id: 101, title: "hub-t1" },
|
|
{ id: 102, title: "hub-t2" },
|
|
],
|
|
total: 8,
|
|
},
|
|
reading: {
|
|
items: [
|
|
{ id: 2, title: "r1" },
|
|
{ id: 3, title: "r2" },
|
|
],
|
|
total: 6,
|
|
},
|
|
},
|
|
posts: {
|
|
items: [
|
|
{ slug: "legacy-p1", title: "legacy-post-1" },
|
|
{ slug: "legacy-p2", title: "legacy-post-2" },
|
|
{ slug: "legacy-p3", title: "legacy-post-3" },
|
|
{ slug: "legacy-p4", title: "legacy-post-4" },
|
|
],
|
|
total: 9,
|
|
},
|
|
timeline: {
|
|
items: [
|
|
{ id: 201, title: "legacy-t1" },
|
|
{ id: 202, title: "legacy-t2" },
|
|
{ id: 203, title: "legacy-t3" },
|
|
],
|
|
total: 11,
|
|
},
|
|
rssItems: {
|
|
items: [
|
|
{ id: 2, title: "r1" },
|
|
{ id: 3, title: "r2" },
|
|
{ id: 4, title: "r3" },
|
|
{ id: 5, title: "r4" },
|
|
{ id: 6, title: "r5" },
|
|
],
|
|
total: 6,
|
|
},
|
|
}));
|
|
|
|
const parseSocialLinksJson = mock(() => [
|
|
{ label: "x", href: "https://x.com/demo", visibility: "public" },
|
|
{ label: "mail", href: "mailto:demo@example.com", visibility: "private" },
|
|
]);
|
|
|
|
mock.module("drizzle-pkg/lib/db", () => ({
|
|
dbGlobal: {
|
|
select: selectMock,
|
|
},
|
|
}));
|
|
|
|
mock.module("drizzle-pkg/lib/schema/auth", () => ({
|
|
users: {
|
|
publicSlug: "publicSlug",
|
|
status: "status",
|
|
},
|
|
}));
|
|
|
|
mock.module("drizzle-orm", () => ({
|
|
and: (...conditions: unknown[]) => conditions,
|
|
eq: (a: unknown, b: unknown) => [a, b],
|
|
}));
|
|
|
|
mock.module("#server/service/profile", () => ({
|
|
parseSocialLinksJson,
|
|
}));
|
|
|
|
mock.module("#server/service/public-hub", () => ({
|
|
getPublicHubBySlug,
|
|
}));
|
|
|
|
globalThis.defineEventHandler ??= ((handler: unknown) => handler) as never;
|
|
globalThis.createError ??= ((input: { statusCode: number; statusMessage: string }) => {
|
|
const error = new Error(input.statusMessage) as Error & {
|
|
statusCode: number;
|
|
statusMessage: string;
|
|
};
|
|
error.statusCode = input.statusCode;
|
|
error.statusMessage = input.statusMessage;
|
|
return error;
|
|
}) as never;
|
|
globalThis.R ??= {
|
|
success: (data: unknown) => data,
|
|
};
|
|
|
|
const handler = (await import("./[publicSlug].get")).default;
|
|
|
|
describe("GET /api/public/profile/:publicSlug", () => {
|
|
beforeEach(() => {
|
|
ownerRecord.publicSlug = "demo-user";
|
|
ownerRecord.nickname = "Demo";
|
|
ownerRecord.avatar = "https://cdn.example.com/avatar.png";
|
|
ownerRecord.avatarVisibility = "public";
|
|
ownerRecord.bioVisibility = "public";
|
|
ownerRecord.bioMarkdown = "hello bio";
|
|
ownerRecord.socialLinksJson = "[]";
|
|
ownerRecord.status = "active";
|
|
selectMock.mockClear();
|
|
getPublicHubBySlug.mockClear();
|
|
parseSocialLinksJson.mockClear();
|
|
});
|
|
|
|
test("通过 public-hub 一次调用返回 modules 与 legacy 字段", async () => {
|
|
const result = await handler({
|
|
context: {
|
|
params: {
|
|
publicSlug: "demo-user",
|
|
},
|
|
},
|
|
} as never);
|
|
|
|
expect(getPublicHubBySlug).toHaveBeenCalledWith("demo-user");
|
|
expect(getPublicHubBySlug).toHaveBeenCalledTimes(1);
|
|
expect(parseSocialLinksJson).toHaveBeenCalledTimes(1);
|
|
expect(selectMock).toHaveBeenCalledTimes(1);
|
|
expect(result).toEqual({
|
|
user: {
|
|
publicSlug: "demo-user",
|
|
nickname: "Demo",
|
|
avatar: "https://cdn.example.com/avatar.png",
|
|
},
|
|
bio: {
|
|
markdown: "hello bio",
|
|
},
|
|
links: [{ label: "x", href: "https://x.com/demo", visibility: "public" }],
|
|
modules: {
|
|
posts: {
|
|
items: [
|
|
{ slug: "hub-p1", title: "hub-post-1" },
|
|
{ slug: "hub-p2", title: "hub-post-2" },
|
|
],
|
|
total: 6,
|
|
},
|
|
timeline: {
|
|
items: [
|
|
{ id: 101, title: "hub-t1" },
|
|
{ id: 102, title: "hub-t2" },
|
|
],
|
|
total: 8,
|
|
},
|
|
reading: {
|
|
items: [
|
|
{ id: 2, title: "r1" },
|
|
{ id: 3, title: "r2" },
|
|
],
|
|
total: 6,
|
|
},
|
|
},
|
|
posts: {
|
|
items: [
|
|
{ slug: "legacy-p1", title: "legacy-post-1" },
|
|
{ slug: "legacy-p2", title: "legacy-post-2" },
|
|
{ slug: "legacy-p3", title: "legacy-post-3" },
|
|
{ slug: "legacy-p4", title: "legacy-post-4" },
|
|
],
|
|
total: 9,
|
|
},
|
|
timeline: {
|
|
items: [
|
|
{ id: 201, title: "legacy-t1" },
|
|
{ id: 202, title: "legacy-t2" },
|
|
{ id: 203, title: "legacy-t3" },
|
|
],
|
|
total: 11,
|
|
},
|
|
rssItems: {
|
|
items: [
|
|
{ id: 2, title: "r1" },
|
|
{ id: 3, title: "r2" },
|
|
{ id: 4, title: "r3" },
|
|
{ id: 5, title: "r4" },
|
|
{ id: 6, title: "r5" },
|
|
],
|
|
total: 6,
|
|
},
|
|
});
|
|
});
|
|
|
|
test("publicSlug 缺失时返回 400,且不触发下游 service", async () => {
|
|
await expect(
|
|
handler({
|
|
context: {
|
|
params: {},
|
|
},
|
|
} as never),
|
|
).rejects.toMatchObject({
|
|
statusCode: 400,
|
|
statusMessage: "无效主页",
|
|
});
|
|
|
|
expect(selectMock).toHaveBeenCalledTimes(0);
|
|
expect(parseSocialLinksJson).toHaveBeenCalledTimes(0);
|
|
expect(getPublicHubBySlug).toHaveBeenCalledTimes(0);
|
|
});
|
|
|
|
test("publicSlug 非法时返回 400,且不触发下游 service", async () => {
|
|
await expect(
|
|
handler({
|
|
context: {
|
|
params: {
|
|
publicSlug: 123,
|
|
},
|
|
},
|
|
} as never),
|
|
).rejects.toMatchObject({
|
|
statusCode: 400,
|
|
statusMessage: "无效主页",
|
|
});
|
|
|
|
expect(selectMock).toHaveBeenCalledTimes(0);
|
|
expect(parseSocialLinksJson).toHaveBeenCalledTimes(0);
|
|
expect(getPublicHubBySlug).toHaveBeenCalledTimes(0);
|
|
});
|
|
|
|
test("owner 不存在或非 active 时返回 404", async () => {
|
|
selectMock.mockImplementationOnce(() => ({
|
|
from: () => ({
|
|
where: () => ({
|
|
limit: async () => [],
|
|
}),
|
|
}),
|
|
}));
|
|
|
|
await expect(
|
|
handler({
|
|
context: {
|
|
params: {
|
|
publicSlug: "demo-user",
|
|
},
|
|
},
|
|
} as never),
|
|
).rejects.toMatchObject({
|
|
statusCode: 404,
|
|
statusMessage: "未找到",
|
|
});
|
|
|
|
expect(selectMock).toHaveBeenCalledTimes(1);
|
|
expect(parseSocialLinksJson).toHaveBeenCalledTimes(0);
|
|
expect(getPublicHubBySlug).toHaveBeenCalledTimes(0);
|
|
});
|
|
|
|
test("avatarVisibility 非 public 时 avatar 为 null", async () => {
|
|
ownerRecord.avatarVisibility = "private";
|
|
|
|
const result = await handler({
|
|
context: {
|
|
params: {
|
|
publicSlug: "demo-user",
|
|
},
|
|
},
|
|
} as never);
|
|
|
|
expect(result.user.avatar).toBeNull();
|
|
expect(selectMock).toHaveBeenCalledTimes(1);
|
|
expect(parseSocialLinksJson).toHaveBeenCalledTimes(1);
|
|
expect(getPublicHubBySlug).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
test("bioVisibility 非 public 时 bio 为 null", async () => {
|
|
ownerRecord.bioVisibility = "private";
|
|
|
|
const result = await handler({
|
|
context: {
|
|
params: {
|
|
publicSlug: "demo-user",
|
|
},
|
|
},
|
|
} as never);
|
|
|
|
expect(result.bio).toBeNull();
|
|
expect(selectMock).toHaveBeenCalledTimes(1);
|
|
expect(parseSocialLinksJson).toHaveBeenCalledTimes(1);
|
|
expect(getPublicHubBySlug).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
test("无 bioMarkdown 时 bio 为 null", async () => {
|
|
ownerRecord.bioMarkdown = "";
|
|
|
|
const result = await handler({
|
|
context: {
|
|
params: {
|
|
publicSlug: "demo-user",
|
|
},
|
|
},
|
|
} as never);
|
|
|
|
expect(result.bio).toBeNull();
|
|
expect(selectMock).toHaveBeenCalledTimes(1);
|
|
expect(parseSocialLinksJson).toHaveBeenCalledTimes(1);
|
|
expect(getPublicHubBySlug).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|
|
|